# -*- coding: utf-8 -*-
# @author : wanglei
# @date : 2020/5/18 21:41
# @description :


from manimlib.imports import *
from from_victor.imports import *
import pandas as pd


class PerceptronModel(Scene):
    '''
    感知器模型
    '''
    captions=[
        '人类对人工智能的研究要追溯到1943年'
        '那一年，神经生物学家沃伦.麦卡洛克（Warren McCullock）和数学家沃尔特.皮茨（Walter Pitts）提出了第一个脑神经元的抽象模型'
        '称为麦卡洛克-皮茨神经元（McCullock-Pitts neuron，即MCP）'
        '几年后弗兰克.罗森布拉特（Frank Rossenblatt）基于MCP神经元模型提出了第一个感知器学习模型'
        '该模型包括几个部分，第一部分是输入'
        '输入是现实世界中物体的特征值，例如：物体的长宽，颜色等'
        '第二部分是输入的处理函数'
        '输入的处理函数在这里是一个累加函数，是对输入的每个特征值乘以它的权重，然后加和，再加上偏置值b'
        '第三部分是激励函数'
        '处理函数的结果会经过激励函数的计算'
        '第四部分是输出'
        '激励函数计算的结果就是最终的输出结果'
        '这就是人类做出的最早的模拟神经元的模型'
    ]
    def construct(self):
        # 神经元
        neure_img = ImageMobject('m-p.jpg')
        neure_img.set_width(7.5)
        neure_img.move_to(4*UP)
        neure_text = TextMobject('图1：生物神经元和MP模型简图').scale(0.4)
        neure_text.next_to(neure_img, BOTTOM, buff=0.2*SMALL_BUFF)
        self.play(FadeIn(neure_img), FadeIn(neure_text))
        self.wait(12)
        #  罗森布拉特（右）和合作伙伴调试感知机
        rosenblatt_img = ImageMobject('罗森布拉特（右）和合作伙伴调试感知机.jpg')
        rosenblatt_img.set_width(7.5)
        rosenblatt_img.next_to(neure_text, BOTTOM, buff=SMALL_BUFF)
        rosenblatt_text = TextMobject('图2：罗森布拉特（右）和合作伙伴调试感知机').scale(0.4)
        rosenblatt_text.next_to(rosenblatt_img, BOTTOM, buff=0.2*SMALL_BUFF)
        self.play(FadeIn(rosenblatt_img), FadeIn(rosenblatt_text))
        self.wait(6)
        self.play(FadeOutAndShift(rosenblatt_img), FadeOutAndShift(rosenblatt_text))
        self.wait(2)
        # 输入
        c1 = Circle(radius=0.4)
        c1.move_to(3*LEFT + 0.7*UP)
        c2 = Circle(radius=0.4)
        c2.next_to(c1, DOWN)
        c3 = Circle(radius=0.4)
        c3.next_to(c2, DOWN)
        c4 = Circle(radius=0.4)
        c4.next_to(c3, DOWN)
        x1 = TexMobject('x_{0}').scale(0.6)
        x1.move_to(c1.get_center())
        x2 = TexMobject('x_{1}').scale(0.6)
        x2.move_to(c2.get_center())
        x3 = TexMobject('x_{2}').scale(0.6)
        x3.move_to(c3.get_center())
        x4 = TexMobject('1').scale(0.6)
        x4.move_to(c4.get_center())

        self.play(Write(c1), Write(c2), Write(c3), Write(c4), Write(x1), Write(x2), Write(x3), Write(x4))
        self.wait(3)
        # 累加
        layer1_gp = VGroup(c1,c2,c3,c4)
        add_c = Circle(radius=0.4)
        add_c.next_to(layer1_gp, 3 * RIGHT)
        sum = TexMobject('\\sum').scale(0.6)
        sum.move_to(add_c.get_center())
        sum_detail = TexMobject('\\sum\\limits_{i=0}^{2}{w_{i}x_{i}}+b').scale(0.6)
        sum_detail.next_to(add_c, UP, aligned_edge=LEFT)
        self.play(Write(add_c), Write(sum))
        self.play(Write(sum_detail))
        self.wait()
        # 权重连接
        l1 = Line(c1.get_center()+c1.radius*RIGHT, add_c.get_center()-add_c.radius*RIGHT, color=BLUE).set_opacity(0.8)
        w1 = TexMobject('w_{0}').scale(0.6)
        w1.move_to(l1.get_center())
        l2 = Line(c2.get_center()+c2.radius*RIGHT, add_c.get_center()-add_c.radius*RIGHT, color=BLUE).set_opacity(0.8)
        w2 = TexMobject('w_{1}').scale(0.6)
        w2.move_to(l2.get_center())
        l3 = Line(c3.get_center() + c3.radius*RIGHT, add_c.get_center() - add_c.radius*RIGHT, color=BLUE).set_opacity(0.8)
        w3 = TexMobject('w_{2}').scale(0.6)
        w3.move_to(l3.get_center())
        l4 = Line(c4.get_center() + c4.radius*RIGHT, add_c.get_center() - add_c.radius*RIGHT, color=BLUE).set_opacity(0.8)
        b = TexMobject('b').scale(0.6)
        b.move_to(l4.get_center())
        self.play(Write(l1), Write(l2), Write(l3), Write(l4), Write(w1), Write(w2), Write(w3), Write(b))
        self.wait(3)
        # 激活函数
        fx_c = Circle(radius=0.4)
        fx_c.next_to(add_c, 3*RIGHT)
        fx = TexMobject('f(x)').scale(0.6)
        fx.move_to(fx_c.get_center())
        self.play(Write(fx_c), Write(fx))
        # 解释激活函数形式
        fx_detail = TexMobject('f(x)=\\begin{cases}'
                               '1 & x>0\\\ '
                               '0 & x\\le0 \\\ '
                               '\\end{cases}').scale(0.6)
        fx_detail.next_to(fx_c, DOWN, aligned_edge=LEFT)
        self.play(Write(fx_detail))
        # 连接
        l5 = Line(add_c.get_center()+add_c.radius*RIGHT, fx_c.get_center()-fx_c.radius *RIGHT, color=BLUE).set_opacity(0.8)
        self.play(Write(l5))
        self.wait(3)
        # 输出
        c5 = Circle(radius=0.4)
        c5.next_to(fx_c, 3*RIGHT)
        output = TextMobject('y').scale(0.6)
        output.move_to(c5.get_center())
        self.play(Write(c5), Write(output))
        # 连接
        l6 = Line(fx_c.get_center()+fx_c.radius *RIGHT, c5.get_center()-c5.radius*RIGHT, color=BLUE).set_opacity(0.8)
        self.play(Write(l6))
        self.wait(3)

class PerceptronIris1(Scene):
    '''
    鸢尾花分类数据
    '''
    captions = [
        '有一种花叫鸢尾花，它是法国的国花'
        '鸢尾花有很多种类别，我们这里说两类，一类叫杂色鸢尾，一类叫山鸢尾'
        '人们可以从花萼和花瓣的宽度这两个特征值来区分是杂色鸢尾还是山鸢尾'
        '通过测量可以得到一批杂色鸢尾和山鸢尾的数据，'
        '鸢尾花数据经常用来作为感知器分类的训练数据'
    ]
    def construct(self):
        # 花的图片
        irises1 = ImageMobject('irises1.jpg')
        irises1.set_width(7.5)
        irises1_text = TextMobject('图1：鸢尾花').scale(0.4)
        irises1_text.next_to(irises1, DOWN, buff=SMALL_BUFF)
        self.play(FadeIn(irises1), FadeIn(irises1_text))
        self.wait(3)
        self.play(FadeOutAndShift(irises1), FadeOutAndShift(irises1_text))
        # 杂色鸢尾和山鸢尾
        irises2 = ImageMobject('irises2.png')
        self.play(FadeIn(irises2))
        self.wait(3)
        self.play(ApplyMethod(irises2.move_to, 4*UP))
        # 表格数据
        df = pd.read_csv("assets/data/Iris.data", header=None)
        # print(df.tail())  # 打印后几行
        y = list(df.iloc[0:3, 4].values) + list(df.iloc[51:54, 4].values)  # 类别
        print(y)
        x1 = list(df.iloc[0:3, 1].values) + list(df.iloc[51:54, 1])  # 花萼宽度
        x2 = list(df.iloc[0:3, 3].values) + list(df.iloc[51:54, 3])  # 花瓣宽度
        print(x1)
        print(x2)
        tabledict = {
            "\\text{花萼宽度}": [str(x) for x in x1],
            "\\text{花瓣宽度}": [str(x) for x in x2],
            "\\text{鸢尾类别}": ['\\text{'+x+'}' for x in y],
        }
        table = Table.get_table(tabledict)
        table.scale(0.50)
        table.move_to((0, 0, 0))
        self.play(Write(table), run_time=3)
        self.wait(2)

class PerceptronWeightUpdate(Scene): # 如何更新呢？误分类点到超平面S的总距离迭代到0
    '''
    感知器权重更新的依据（求最小化距离）【其实就是损失函数（优化函数、目标函数），只是损失函数这个概念是到了神经网络才提出的】
    '''
    captions = [
        '如果我们想通过感知器模型来对鸢尾花分类，分为两类，一类是杂色鸢尾，一类是山鸢尾'
        '我们需要先弄清感知器是如何训练的，训练的依据是什么？真实神经元的这些w、b其实是固定的'
        '因为输入是鸢尾花的花萼和花瓣的宽度两个特征值，所以处理函数就变成了w0x0+w1x1+b'
        '对于鸢尾花，我们可以利用x轴表示花瓣的宽度，利用y轴表示花萼的宽度'
        '那么对于每一个样本就可以用点来表示，我们就可以找到一条直线将样本分开'
        '因此误分类点到直线的总距离就可以作为感知器权重更新的依据，这条直线就是w0x0+w1x1+b=0'
        '假设第一次找到了一条直线，本次出现了2个误分类点，那么总距离就是s1+s2，'
        '我们想要总距离不断减小，就要不断调整这条直线，直到总距离为0，直线能完全将样本分开为止'
        '那么对于高维度，就是不断调整这个超平面，使误分类点到超平面的距离不断减小，直到为0'
    ]
    def construct(self):
        irises1 = ImageMobject('irises1.jpg')
        irises1.move_to(3*UP)
        self.add(irises1)
        irises1.set_width(4)
        irises1_text = TextMobject('图1：鸢尾花').scale(0.4)
        self.add(irises1_text)
        irises1_text.next_to(irises1, UP, buff=SMALL_BUFF)
        self.play(FadeIn(irises1), FadeIn(irises1_text))
        self.wait(2)
        # 杂色鸢尾和山鸢尾
        irises2 = ImageMobject('irises2.png')
        irises2.move_to(2*DOWN)
        self.add(irises2)
        self.play(FadeIn(irises2))
        self.wait()
        # 箭头
        arrow1 = Arrow(irises1.get_center() + 1.5 * DOWN, irises2.get_start() + 0.5 * RIGHT)
        arrow2 = Arrow(irises1.get_center() + 1.5 * DOWN, irises2.get_start() + 4 * RIGHT)
        self.play(Write(arrow1), Write(arrow2))
        self.wait(4)
        self.clear()

        c1 = Circle(radius=0.4)
        c1.move_to(2.5 * LEFT + UP)
        c2 = Circle(radius=0.4)
        c2.next_to(c1, DOWN)
        c3 = Circle(radius=0.4)
        c3.next_to(c2, DOWN)
        c4 = Circle(radius=0.4)
        c4.next_to(c3, DOWN)
        x1 = TexMobject('x_{0}').scale(0.6)
        x1.move_to(c1.get_center())
        x2 = TexMobject('x_{1}').scale(0.6)
        x2.move_to(c2.get_center())
        x3 = TexMobject('x_{2}').scale(0.6)
        x3.move_to(c3.get_center())
        x4 = TexMobject('1').scale(0.6)
        x4.move_to(c4.get_center())

        self.play(Write(c1), Write(c2), Write(c3), Write(c4), Write(x1), Write(x2), Write(x3), Write(x4))
        # 累加
        layer1_gp = VGroup(c1, c2, c3, c4)
        add_c = Circle(radius=0.4)
        add_c.next_to(layer1_gp, 3 * RIGHT)
        sum = TexMobject('\\sum').scale(0.6)
        sum.move_to(add_c.get_center())
        sum_detail = TexMobject('\\sum\\limits_{i=0}^{2}{w_{i}x_{i}}+b').scale(0.6)
        sum_detail.next_to(add_c, UP, aligned_edge=LEFT)
        self.play(Write(add_c), Write(sum), Write(sum_detail))
        # 权重连接
        l1 = Line(c1.get_center() + c1.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        w1 = TexMobject('w_{0}').scale(0.6)
        w1.move_to(l1.get_center())
        l2 = Line(c2.get_center() + c2.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        w2 = TexMobject('w_{1}').scale(0.6)
        w2.move_to(l2.get_center())
        l3 = Line(c3.get_center() + c3.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        w3 = TexMobject('w_{2}').scale(0.6)
        w3.move_to(l3.get_center())
        l4 = Line(c4.get_center() + c4.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        b = TexMobject('b').scale(0.6)
        b.move_to(l4.get_center())
        self.play(Write(l1), Write(l2), Write(l3), Write(l4), Write(w1), Write(w2), Write(w3), Write(b))
        # 标记出w和b
        self.play(ShowCreationThenFadeAround(w1), ShowCreationThenFadeAround(w2), ShowCreationThenFadeAround(w3),
                  ShowCreationThenFadeAround(b))
        # 激活函数
        fx_c = Circle(radius=0.4)
        fx_c.next_to(add_c, 3 * RIGHT)
        fx = TexMobject('f(x)').scale(0.6)
        fx.move_to(fx_c.get_center())
        # 解释激活函数形式
        fx_detail = TexMobject('f(x)=\\begin{cases}'
                               '1 & x>0\\\ '
                               '0 & x\\le0 \\\ '
                               '\\end{cases}').scale(0.6)
        fx_detail.next_to(fx_c, DOWN, aligned_edge=LEFT)
        self.play(Write(fx_c), Write(fx), Write(fx_detail))
        # 连接
        l5 = Line(add_c.get_center() + add_c.radius * RIGHT, fx_c.get_center() - fx_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        # 输出
        c5 = Circle(radius=0.4)
        c5.next_to(fx_c, 3 * RIGHT)
        output = TextMobject('y').scale(0.6)
        output.move_to(c5.get_center())
        # 连接
        l6 = Line(fx_c.get_center() + fx_c.radius * RIGHT, c5.get_center() - c5.radius * RIGHT, color=BLUE).set_opacity(
            0.8)
        self.play(Write(l6), Write(l5), Write(c5), Write(output))
        self.wait()

        # 处理函数
        new_sum_detail = TexMobject('w_{0}x_{0}+w_{1}x_{1}+b', color=YELLOW).scale(0.6)
        new_sum_detail.move_to(sum_detail.get_center())
        self.play(ShowCreationThenFadeAround(sum_detail))
        self.play(FadeOut(c3), FadeOut(x3), FadeOut(l3), FadeOut(w3), Transform(sum_detail, new_sum_detail))
        self.wait(6)
        self.clear()


        # 读取数据
        df = pd.read_csv("assets/data/Iris.data", header=None)
        y = df.iloc[0:100:10, 4].values  # 类别
        X = df.iloc[0:100:10, [1, 3]].values  # 花萼宽度、花瓣宽度
        '''
        X = 
        [[3.5 0.2]
         [3.7 0.2]
         [3.4 0.2]
         [3.1 0.2]
         [3.5 0.3]----
         [3.2 1.4] 错误点
         [2.  1. ]
         [3.2 1.8] 错误点
         [2.4 1.1]
         [2.6 1.2]]
         让切分线为 x=3.0 即可 
        '''
        self.distance_show(X, y)
        # 权重更新

    def distance_show(self, X, y):
        # 创建坐标轴
        axes = Axes(
            x_min=0,
            x_max=6,
            y_min=0,
            y_max=6,
            y_axis_config={"unit_size": 0.5},
            center_point=2.5 * LEFT
        )
        x_axis_label = TextMobject('花瓣宽度').scale(0.6)
        y_axis_label = TextMobject('花\\\萼\\\宽\\\度').scale(0.6)
        x_axis_label.next_to(axes.x_axis, DOWN, aligned_edge=RIGHT)
        y_axis_label.next_to(axes.y_axis, LEFT, aligned_edge=TOP)
        self.play(Write(axes))
        self.play(Write(x_axis_label))
        self.play(Write(y_axis_label))
        # 图例
        c = Circle(radius=0.02, color=RED)
        c.next_to(y_axis_label, DOWN, aligned_edge=LEFT, buff=4*LARGE_BUFF)
        c_text = TextMobject('山鸢尾', color=RED).scale(0.8)
        c_text.next_to(c, RIGHT)
        s = Square(side_length=0.1, color=BLUE)
        s.next_to(c, DOWN, buff=MED_LARGE_BUFF)
        s_text = TextMobject('杂色鸢尾', color=BLUE).scale(0.8)
        s_text.next_to(s, RIGHT)
        self.play(Write(c), Write(c_text))
        self.play(Write(s), Write(s_text))
        self.wait(3)
        # 散点
        circle_list = []
        square_list = []
        for i, x in enumerate(X):
            # 圆和正方形
            if y[i]=='山鸢尾':
                circle = Circle(arc_center=(x[0]-3)*RIGHT+x[1]*UP, radius=0.02, color=RED) # x[0]-3 为了将x轴开始值调为3
                circle_list.append((circle, x))
                self.play(Write(circle), run_time=0.1)
            else:
                square = Square(side_length=0.1, color=BLUE)
                square.move_to((x[0]-3)*RIGHT+x[1]*UP)
                square_list.append((square, x))
                self.play(Write(square), run_time=0.1)
        self.wait(3)
        # 分割线
        split_line = Line(start=0.3*LEFT+4*UP, end=0.3*LEFT+2*DOWN, color=GREEN)
        split_line_text = TexMobject('w_{0}x_{0}+w_{1}x_{1}+b=0').scale(0.7)
        split_line_text.next_to(split_line, RIGHT, aligned_edge=DOWN)
        self.play(Write(split_line))
        self.play(Write(split_line_text))
        self.wait(6)

        # 错误点到分割线的距离
        error1 = square_list[0]
        error2 = square_list[2]
        error1_dl = DashedLine(start=(error1[1][0] - 3) * RIGHT + error1[1][1] * UP, end=0.3*LEFT+error1[1][1] * UP, color=YELLOW)
        error2_dl = DashedLine(start=(error2[1][0] - 3) * RIGHT + error2[1][1] * UP, end=0.3*LEFT+error2[1][1] * UP, color=YELLOW)

        self.play(Write(error1_dl))
        self.play(Write(error2_dl))
        self.wait()

        # 表述距离，无限接近于0，如果是线性可分的，那么最终错误分类个数为0时，距离也就为0了。
        s1 = TextMobject('s1', color=YELLOW)
        s1.next_to(split_line.get_start(), RIGHT)
        add = TextMobject('+')
        add.next_to(s1, RIGHT)
        s2 = TextMobject('s2', color=YELLOW)
        s2.next_to(add, RIGHT)
        self.play(TransformFromCopy(error1_dl, s1), Write(add), TransformFromCopy(error2_dl, s2))
        self.wait()
        distance_gp = VGroup(s1, add , s2)
        self.play(ShowCreationThenFadeAround(distance_gp))
        arrow = TexMobject('\\rightarrow')
        arrow.next_to(s2, RIGHT)
        zero = TextMobject('0', color=YELLOW)
        zero.next_to(arrow, RIGHT)
        self.play(Write(arrow))
        self.play(Write(zero))
        self.wait(3)
        # 调整分割线
        self.play(FadeOut(error1_dl), FadeOut(error2_dl))
        split_line2 = Line(start=4 * RIGHT + 4 * UP, end=3 * LEFT + 2 * DOWN, color=GREEN)
        self.play(Transform(split_line, split_line2), ApplyMethod(split_line_text.move_to, 0.5*LEFT + 2 * DOWN), run_time=2)
        self.wait(6)







    def animation_classify(self, X, y):
        # 创建坐标轴
        axes = Axes(
            x_min=0,
            x_max=6,
            y_min=0,
            y_max=6,
            y_axis_config={"unit_size": 0.5},
            center_point=2 * LEFT
        )
        self.play(Write(axes))
        # 散点
        circle_list = []
        square_list = []
        for i, x in enumerate(X):
            # 圆和正方形
            if y[i]=='山鸢尾':
                circle = Circle(arc_center=(x[0]-4)*RIGHT+x[1]*UP, radius=0.005, color=RED)
                circle_list.append(circle)
                self.play(Write(circle))
            else:
                square = Square(side_length=0.01, color=BLUE)
                square.move_to((x[0]-4)*RIGHT+x[1]*UP)
                square_list.append(square)
                self.play(Write(square))

        # 分割线
        # split_line = Line(start=, end=, color=GREEN)


class PerceptronDerivatives(GraphScene):
    '''
    导数的几何意义
    '''
    captions = [
        '导数的几何意义'
        '某点的导数，其实就是过该点切线的斜率'
    ]
    CONFIG = {
        "x_min": -3,
        "x_max": 3,  # X坐标范围
        "x_tick_frequency": 1,  # X坐标刻度密度
        "x_labeled_nums": range(-3, 4, 1),  # X坐标刻度数值
        "x_axis_label": "$x$",  # X坐标标注
        "y_min": 0,
        "y_max": 10,  # Y坐标范围
        # "y_tick_frequency": 1,  # Y坐标刻度密度
        # "y_labeled_nums": range(0, 11, 1),  # Y坐标刻度数值
        "y_axis_label": "$y$",  # Y坐标标注
        "exclude_zero_label": True,  # 不显示Y坐标的0刻度

        "y_axis_height": 5,  # 坐标轴高度(屏幕高被分为 8 份)
        "x_axis_width": 5,  # 坐标轴宽度(屏幕宽被分为 14 份)
        "graph_origin": ORIGIN
        # 原点的位置（ORIGIN - 屏幕中心，TOP、BOTTOM、LEFT_SIDE、RIGHT_SIDE 分别指上下左右侧
    }
    def construct(self):
        self.setup_axes()
        def func(x):
            return x**2
        graph = self.get_graph(func, color=RED)
        # 给 graph 函数图像，增加函数标签，位于 x = 8 处，位于上方
        graph_label = self.get_graph_label(graph,
                                           label="f(x) = x^{2}",
                                           x_val=8, direction=4 * UP)
        graph_label.set_color_by_gradient(BLUE)

        # 同时绘制这两个图像
        self.play(ShowCreation(graph), ShowCreation(graph_label), run_time=1)

        # 某点的导数，其实就是过该点切线的斜率
        # 切线、点、斜率
        t_tracker = ValueTracker(0)
        get_t = t_tracker.get_value
        def tangent_line_update(obj):
            # print('get_t:', get_t())
            # print('get_t() / self.CONFIG: ', get_t() / self.CONFIG['x_max'])
            p0, slope, tangent_line = self.get_tangent_line(
                graph, get_t() / self.CONFIG['x_max']
            )
            obj.become(tangent_line)

        def point_text_update(obj):
            p0, slope, tangent_line = self.get_tangent_line(
                graph, get_t() / self.CONFIG['x_max']
            )
            # print('b', p0)
            point_text = TextMobject('(' + str(p0[0]) + ',' + str(p0[1]) + ')',
                                     tex_to_color_map={str(p0[0]): RED, str(p0[1]): RED}).scale(0.7)
            point_text.next_to(text1, RIGHT)
            obj.become(point_text)

        def slope_text_update(obj):
            p0, slope, tangent_line = self.get_tangent_line(
                graph, get_t() / self.CONFIG['x_max']
            )
            slope_text = TextMobject(str(slope), tex_to_color_map={str(slope): BLUE}).scale(0.7)
            slope_text.next_to(text2, RIGHT)
            obj.become(slope_text)


        p0, slope, tangent_line = self.get_tangent_line(graph, get_t() / self.CONFIG['x_max'])
        # print('b', p0)

        text1 = TextMobject('点:').scale(0.7)
        text1.move_to(2 * RIGHT + 2 * UP)
        text2 = TextMobject('斜率:').scale(0.7)
        text2.next_to(text1, DOWN, aligned_edge=LEFT)
        point_text = TextMobject('('+str(p0[0])+','+str(p0[1])+')', tex_to_color_map={str(p0[0]):RED, str(p0[1]):RED}).scale(0.7)
        slope_text = TextMobject(str(slope), tex_to_color_map={str(slope):BLUE}).scale(0.7)
        point_text.next_to(text1, RIGHT)
        slope_text.next_to(text2, RIGHT)
        tangent_line.add_updater(tangent_line_update)
        point_text.add_updater(point_text_update)
        slope_text.add_updater(slope_text_update)
        self.add(text1)
        self.add(text2)
        self.add(tangent_line)
        self.add(point_text)
        self.add(slope_text)
        self.play(t_tracker.increment_value, 3, rate_func=linear, run_time=4)

    def get_tangent_line(self, curve, alpha):
        line = Line(
            ORIGIN, 3 * RIGHT,
            color=BLUE,
            stroke_width=3,
        )
        da = 0.0001
        p0 = curve.point_from_proportion(alpha) # TODO: p0点并不是x**2上的点，不知道为啥
        p1 = curve.point_from_proportion(alpha - da)
        p2 = curve.point_from_proportion(alpha + da)
        angle = angle_of_vector(p2 - p1) # 倾斜角
        line.rotate(angle)
        line.move_to(p0)
        # print('a:',p0)
        slope = round(2*p0[0], 2)# TODO: (p2[1]-p1[1])/(p2[0]-p1[0])这个不准不知道为啥 # 斜率
        return [round(p0[0],2), round(p0[0]**2,2)], slope, line


class PerceptronPartialDerivatives(ThreeDScene):
    '''
    偏导数的几何意义
    '''
    captions=[
        '偏导数的几何意义'
        '有一个曲面，我们确定一点，'
        '过该点标出平行于zx轴平面的曲线'
        '过该点作曲线的切线，该切线的斜率就是x的偏导数'
        '过该点标出平行于zy轴平面的曲线'
        '过该点作曲线的切线，该切线的斜率就是y的偏导数'
    ]
    def construct(self):
        config = {
            "dimension": 3,
            "x_min": -4,
            "x_max": 4,
            "y_min": -4,
            "y_max": 4,
            "z_axis_config": {},
            "z_min": -4,
            "z_max": 4,
            "z_normal": DOWN,
            "num_axis_pieces": 20,
        }
        axes = ThreeDAxes(**config)
        axes.x_axis.set_color(RED)
        axes.y_axis.set_color(GREEN)
        axes.z_axis.set_color(BLUE)

        x_axis_label = TexMobject('x')
        x_axis_label.rotate(PI / 2, axis=RIGHT)
        x_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(x_axis_label)
        x_axis_label.move_to(axes.x_axis.get_end())
        y_axis_label = TexMobject('y')
        y_axis_label.rotate(PI / 2, axis=RIGHT)
        y_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(y_axis_label)
        y_axis_label.move_to(axes.y_axis.get_end())
        z_axis_label = TexMobject('z')
        z_axis_label.rotate(PI / 2, axis=RIGHT)
        z_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(z_axis_label)
        z_axis_label.move_to(axes.z_axis.get_end())
        axes.add(x_axis_label)
        axes.add(y_axis_label)
        axes.add(z_axis_label)
        self.set_camera_orientation(phi=60 * DEGREES, theta=1 * DEGREES, distance=10)
        self.add(axes)
        # surface = ParametricSurface(
        #     lambda u, v: np.array([
        #         np.cos(v) * u,
        #         np.sin(v) * u,
        #         -u ** 2
        #     ]), v_max=TAU,
        #     checkerboard_colors=[PURPLE_D, PURPLE_E],
        #     resolution=(10, 32)).scale(2)

        # 曲面
        surface = ParametricSurface(
            lambda u,v :np.array([u, v, 2-u ** 2 - v ** 2]),
            v_min=-1,v_max=1,u_min=-1,u_max=1, checkerboard_colors=[PURPLE_D, PURPLE_E], resolution=(10, 20), fill_opacity=0.9)
        self.play(ShowCreation(surface), run_time=2)
        self.begin_ambient_camera_rotation(rate=0.2)
        self.wait()
        # 标记点
        dot = Dot(axes.c2p(0.5, 0.5, 1.5), color=YELLOW)
        # dot_text = TexMobject('p_{0}=(x_{0},y_{0},z_{0})', color=YELLOW).scale(0.7)
        # dot_text.move_to(dot)
        # dot_text.rotate(PI / 2, axis=RIGHT)
        # dot_text.rotate(PI / 2, axis=OUT)
        self.play(Write(dot))
        self.wait()
        # self.play(Write(dot_text))
        # 过点平行于zx平面的曲线
        zx_curve = ParametricFunction(
            lambda t: np.array([t, 0.5, 2-t ** 2 - 0.5 ** 2]),
            t_min=-1 ,t_max=1, color=RED
        )
        # zx_curve_text = TexMobject('f(x,y_{0})', color=RED).scale(0.7)
        # zx_curve_text.move_to(zx_curve.get_start())
        # zx_curve_text.rotate(PI / 2, axis=RIGHT)
        # zx_curve_text.rotate(PI / 2, axis=OUT)
        self.play(Write(zx_curve))
        self.wait()
        # self.play(Write(zx_curve_text))
        # 过点的切线，x偏导数
        zx_line = ParametricFunction(
            lambda t: np.array([t, 0.5, 2-t]),
            t_min=-2 ,t_max=2, color=RED
        )
        # zx_line_text = TexMobject('f\'_{x}(x_{0},y_{0})', color=RED).scale(0.7)
        # zx_line_text.move_to(zx_line.get_start())
        # zx_line_text.rotate(PI / 2, axis=RIGHT)
        # zx_line_text.rotate(PI / 2, axis=OUT)
        self.play(Write(zx_line))
        self.wait()
        # self.play(Write(zx_line_text))

        # 过点平行于zy平面的曲线
        zx_curve = ParametricFunction(
            lambda t: np.array([0.5, t, 2 - t ** 2 - 0.5 ** 2]),
            t_min=-1, t_max=1, color=GREEN
        )
        self.play(Write(zx_curve))
        self.wait()
        # 过点的切线，y偏导数
        zx_line = ParametricFunction(
            lambda t: np.array([0.5, t, 2 - t]),
            t_min=-2, t_max=2, color=GREEN
        )
        self.play(Write(zx_line))
        self.wait(7)
        self.stop_ambient_camera_rotation()










class PerceptronDirectionalDerivatives(ThreeDScene):
    '''
    方向导数(偏导数是特殊的方向导数)
    '''
    captions=[
        '过曲面上的一个点存在一个切面'
        '那么就有无数条切线，每条切线的斜率是不一样的'
        '假设每条切线都是有方向的，那么方向应该是任意角度'
        '通常我们使用xy轴组成的平面来衡量方向导数'
        'x轴正方向的导数就是x的偏导数'
        'y轴正方向的导数就是y的偏导数'
    ]
    def construct(self):
        config = {
            "dimension": 3,
            "x_min": -4,
            "x_max": 4,
            "y_min": -4,
            "y_max": 4,
            "z_axis_config": {},
            "z_min": -4,
            "z_max": 4,
            "z_normal": DOWN,
            "num_axis_pieces": 20,
        }
        axes = ThreeDAxes(**config)
        axes.x_axis.set_color(RED)
        axes.y_axis.set_color(GREEN)
        axes.z_axis.set_color(BLUE)

        x_axis_label = TexMobject('x')
        x_axis_label.rotate(PI / 2, axis=RIGHT)
        x_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(x_axis_label)
        x_axis_label.move_to(axes.x_axis.get_end())
        y_axis_label = TexMobject('y')
        y_axis_label.rotate(PI / 2, axis=RIGHT)
        y_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(y_axis_label)
        y_axis_label.move_to(axes.y_axis.get_end())
        z_axis_label = TexMobject('z')
        z_axis_label.rotate(PI / 2, axis=RIGHT)
        z_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(z_axis_label)
        z_axis_label.move_to(axes.z_axis.get_end())
        axes.add(x_axis_label)
        axes.add(y_axis_label)
        axes.add(z_axis_label)
        self.add(axes)
        self.set_camera_orientation(phi=60 * DEGREES, theta=10 * DEGREES, distance=10)
        #xy轴平面
        xy_plane = ParametricSurface(
            lambda u, v: np.array([u, v, 0]),
            v_min=0, v_max=4, u_min=0, u_max=4,
            fill_opacity=0.5,
            fill_color=BLUE_B,
            stroke_width=0.5,
            stroke_color=WHITE, resolution=(10, 20))
        self.play(ShowCreation(xy_plane))
        # 曲面
        surface = ParametricSurface(
            lambda u, v: np.array([u, v, 2 - u ** 2 - v ** 2]),
            v_min=-1, v_max=1, u_min=-1, u_max=1, checkerboard_colors=[PURPLE_D, PURPLE_E], resolution=(10, 20),
            fill_opacity=0.9)
        self.play(ShowCreation(surface), run_time=2)
        self.begin_ambient_camera_rotation(rate=0.2)
        self.wait()
        # 标记点
        dot = Dot(np.array([0.5, 0.5, 1.5]), color=YELLOW)
        self.play(Write(dot))
        self.wait()
        # 切面，在点(0.5, 0.5, 1.5)的切面
        tangent_plane = ParametricSurface(
            lambda u, v: np.array([u, v, 2.5 - u - v]),
            v_min=0, v_max=2.5, u_min=0, u_max=2.5, checkerboard_colors=[RED_D, RED], resolution=(10, 20),
            fill_opacity=0.6)
        self.play(ShowCreation(tangent_plane), run_time=2)


        # 标出方向导数
        import math
        t_tracker = ValueTracker(0)
        def arrow1_update(obj):
            angle = t_tracker.get_value()*DEGREES
            r = 0.5
            x =0.5 + math.sin(angle) * r
            y = 0.5 + math.cos(angle) * r
            z = 2.5-x-y
            arrow = Arrow(np.array([0.5, 0.5, 1.5]), np.array([x, y, z]), buff=0, stroke_width=4, color=YELLOW)
            obj.become(arrow)

        def arrow2_update(obj):
            angle = t_tracker.get_value() * DEGREES
            r = 0.5
            x = 0.5 + math.sin(angle) * r
            y = 0.5 + math.cos(angle) * r
            z = 0
            arrow = Arrow(np.array([0.5, 0.5, 0]), np.array([x, y, z]), buff=0, stroke_width=4, color=YELLOW)
            obj.become(arrow)
        arrow1 = Arrow(np.array([0.5, 0.5, 1.5]), np.array([0, 0, 2.5]), buff=0, stroke_width=4, color=YELLOW)
        arrow1.add_updater(arrow1_update)
        self.play(Write(arrow1))
        arrow2 = Arrow(np.array([0.5, 0.5, 0]), np.array([0, 0, 0]), buff=0, stroke_width=4, color=YELLOW)
        arrow2.add_updater(arrow2_update)
        self.play(Write(arrow2))
        self.play(t_tracker.increment_value, 360, rate_func=linear, run_time=4)
        self.wait(6)
        self.stop_ambient_camera_rotation()



"""
# TODO：这个3D里的角度标注总是不合适，后期在研究吧
class PerceptronGradients(ThreeDScene):
    '''
    梯度
    '''
    captions=[

    ]

    def construct(self):
        config = {
            "dimension": 3,
            "x_min": -4,
            "x_max": 4,
            "y_min": -4,
            "y_max": 4,
            "z_axis_config": {},
            "z_min": -4,
            "z_max": 4,
            "z_normal": DOWN,
            "num_axis_pieces": 20,
        }
        axes = ThreeDAxes(**config)
        axes.x_axis.set_color(RED)
        axes.y_axis.set_color(GREEN)
        axes.z_axis.set_color(BLUE)

        x_axis_label = TexMobject('x')
        x_axis_label.rotate(PI / 2, axis=RIGHT)
        x_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(x_axis_label)
        x_axis_label.move_to(axes.x_axis.get_end())
        y_axis_label = TexMobject('y')
        y_axis_label.rotate(PI / 2, axis=RIGHT)
        y_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(y_axis_label)
        y_axis_label.move_to(axes.y_axis.get_end())
        z_axis_label = TexMobject('z')
        z_axis_label.rotate(PI / 2, axis=RIGHT)
        z_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(z_axis_label)
        z_axis_label.move_to(axes.z_axis.get_end())
        axes.add(x_axis_label)
        axes.add(y_axis_label)
        axes.add(z_axis_label)
        self.add(axes)
        self.set_camera_orientation(phi=60 * DEGREES, theta=45 * DEGREES, distance=10)
        # xy轴平面
        xy_plane = ParametricSurface(
            lambda u, v: np.array([u, v, 0]),
            v_min=0, v_max=4, u_min=0, u_max=4,
            fill_opacity=0.5,
            fill_color=BLUE_B,
            stroke_width=0.5,
            stroke_color=WHITE, resolution=(10, 20))
        self.play(ShowCreation(xy_plane))
        # 曲面
        surface = ParametricSurface(
            lambda u, v: np.array([u, v, 2 - u ** 2 - v ** 2]),
            v_min=-1, v_max=1, u_min=-1, u_max=1, checkerboard_colors=[PURPLE_D, PURPLE_E], resolution=(10, 20),
            fill_opacity=0.9)
        self.play(ShowCreation(surface))
        # 标记点
        dot = Dot(np.array([0.5, 0.5, 1.5]), color=YELLOW)
        self.play(Write(dot))
        # 切面，在点(0.5, 0.5, 1.5)的切面
        tangent_plane = ParametricSurface(
            lambda u, v: np.array([u, v, 2.5 - u - v]),
            v_min=0, v_max=2.5, u_min=0, u_max=2.5, checkerboard_colors=[RED_D, RED], resolution=(10, 20),
            fill_opacity=0.6)
        self.play(ShowCreation(tangent_plane))

        # 连接切面与x、y轴的交点
        line = Line([2.5, 0, 0], [0, 2.5, 0], color=YELLOW)
        self.play(Write(line))
        # 连接[0.5, 0.5, 1.5]和切面与x、y轴的交点
        line_x = Line(dot.get_center(), [2.5, 0, 0], color=YELLOW)
        line_y = Line(dot.get_center(), [0, 2.5, 0], color=YELLOW)
        self.play(Write(line_x), Write(line_y))

        # 标出点[0.5, 0.5, 1.5]在xy轴平面上的投影点[0.5, 0.5, 0]
        dot_project = Dot(np.array([0.5, 0.5, 0]), color=YELLOW)
        self.play(Write(dot_project))

        # 连接过投影点平行于x、y轴的平行线和切面与x、y轴的交线的交点, 以及点和投影点连线
        line_project_x = DashedLine(dot_project.get_center(), [2, 0.5, 0], color=YELLOW)
        line_project_y = DashedLine(dot_project.get_center(), [0.5, 2, 0], color=YELLOW)
        dot_project_line = DashedLine(dot_project.get_center(), dot.get_center(), color=YELLOW)
        self.play(Write(line_project_x), Write(line_project_y), Write(dot_project_line))

        # 连接过点平行于x、y轴的平行线和切面与x、y轴的交线的交点
        line_project_x = Line(dot.get_center(), [2, 0.5, 0], color=YELLOW)
        line_project_y = Line(dot.get_center(), [0.5, 2, 0], color=YELLOW)
        self.play(Write(line_project_x), Write(line_project_y))
        # 标记夹角
        # v1 = Arrow([2, 0.5, 0], dot_project.get_center())
        # v2 = Arrow([2, 0.5, 0], dot.get_center())
        # start_angle, angle = get_arc_angle(v2, v1)
        # angle = math.degrees(math.atan(0.5/1.5))*DEGREES
        # print(angle)
        # TODO:只能用这种方式了，花了很长时间没有找到更好的方式，还是由于对manim的原理不那么明白。后期再研究吧，先解决问题
        arc = Arc(start_angle=0, angle=1, radius=0.5, color=BLACK)
        arc.move_arc_center_to(np.array([1, 0.5, 0]))
        arc.rotate(PI / 2, axis=X_AXIS, about_point=np.array([1.5, 0.5, 0]))
        arc.rotate(PI, axis=Z_AXIS)

        self.play(Write(arc))

        alpha = TexMobject('\\alpha', color=BLACK)
        alpha.rotate(PI / 2, axis=X_AXIS)
        alpha.rotate(PI, axis=Z_AXIS)
        alpha.shift(arc.get_center()+0.3*LEFT)
        self.play(Write(alpha))

        # v1_1 = Arrow([0.5, 2, 0], dot_project.get_center())
        # v2_1 = Arrow([0.5, 2, 0], dot.get_center())
        # start_angle_1, angle_1 = get_arc_angle(v2_1, v1_1)
        arc_1 = Arc(start_angle=0.3, angle=1.3, radius=0.5, color=BLACK)
        arc_1.move_arc_center_to(np.array([0.5, 1, 0]))
        arc_1.rotate(PI / 2, axis=Y_AXIS, about_point=np.array([0.5, 1.5, 0]))
        arc_1.rotate(PI, axis=Z_AXIS)
        self.play(Write(arc_1))
        beta = TexMobject('\\beta', color=BLACK)
        beta.rotate(PI / 2, axis=X_AXIS)
        beta.rotate(PI, axis=Z_AXIS)
        beta.shift(arc_1.get_center() + 0.3 * DOWN)
        self.play(Write(beta))
        self.play(FadeOut(tangent_plane))
        # self.play(Write(beta))
"""

class PerceptronGradients(ThreeDScene):
    '''
    梯度
    '''
    captions=[
        '梯度的本意是一个向量（矢量），表示某一函数在该点处的方向导数沿着该方向取得最大值，'
        '即函数在该点处沿着该方向（此梯度的方向）变化最快，变化率最大（变化率为该梯度的模）'
        '那么方向导数什么时候最大呢？'
        '想让方向导数最大，其实就是夹角的tan值最大，也就是投影点到交线的距离最短'
        '什么时候最短，当然是垂线最短。'
        '梯度的值也就是夹角的tan值正好等于x、y偏导数作为点，从原点指向该点的向量的模'
        '而梯度的方向则指向函数增大的方向，那么梯度的反方向就指向函数减小的方向'
    ]

    def construct(self):
        config = {
            "dimension": 3,
            "x_min": -4,
            "x_max": 4,
            "y_min": -4,
            "y_max": 4,
            "z_axis_config": {},
            "z_min": -4,
            "z_max": 4,
            "z_normal": DOWN,
            "num_axis_pieces": 20,
        }
        axes = ThreeDAxes(**config)
        axes.x_axis.set_color(RED)
        axes.y_axis.set_color(GREEN)
        axes.z_axis.set_color(BLUE)

        x_axis_label = TexMobject('x')
        x_axis_label.rotate(PI / 2, axis=RIGHT)
        x_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(x_axis_label)
        x_axis_label.move_to(axes.x_axis.get_end())
        y_axis_label = TexMobject('y')
        y_axis_label.rotate(PI / 2, axis=RIGHT)
        y_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(y_axis_label)
        y_axis_label.move_to(axes.y_axis.get_end())
        z_axis_label = TexMobject('z')
        z_axis_label.rotate(PI / 2, axis=RIGHT)
        z_axis_label.rotate(PI / 2, axis=OUT)
        # self.add_fixed_in_frame_mobjects(z_axis_label)
        z_axis_label.move_to(axes.z_axis.get_end())
        axes.add(x_axis_label)
        axes.add(y_axis_label)
        axes.add(z_axis_label)
        self.add(axes)
        self.set_camera_orientation(phi=60 * DEGREES, theta=45 * DEGREES, distance=10)
        # xy轴平面
        xy_plane = ParametricSurface(
            lambda u, v: np.array([u, v, 0]),
            v_min=0, v_max=4, u_min=0, u_max=4,
            fill_opacity=0.5,
            fill_color=BLUE_B,
            stroke_width=0.5,
            stroke_color=WHITE, resolution=(10, 20))
        self.play(ShowCreation(xy_plane))
        # 曲面
        surface = ParametricSurface(
            lambda u, v: np.array([u, v, 2 - u ** 2 - v ** 2]),
            v_min=-1, v_max=1, u_min=-1, u_max=1, checkerboard_colors=[PURPLE_D, PURPLE_E], resolution=(10, 20),
            fill_opacity=0.9)
        self.play(ShowCreation(surface))
        # 标记点
        dot = Dot(np.array([0.5, 0.5, 1.5]), color=YELLOW)
        self.play(Write(dot))
        # 切面，在点(0.5, 0.5, 1.5)的切面
        tangent_plane = ParametricSurface(
            lambda u, v: np.array([u, v, 2.5 - u - v]),
            v_min=0, v_max=2.5, u_min=0, u_max=2.5, checkerboard_colors=[RED_D, RED], resolution=(10, 20),
            fill_opacity=0.6)
        self.play(ShowCreation(tangent_plane))

        # 连接切面与x、y轴的交点
        line = Line([2.5, 0, 0], [0, 2.5, 0], color=YELLOW)
        self.play(Write(line))
        # 连接[0.5, 0.5, 1.5]和切面与x、y轴的交点
        line_x = Line(dot.get_center(), [2.5, 0, 0], color=YELLOW)
        line_y = Line(dot.get_center(), [0, 2.5, 0], color=YELLOW)
        self.play(Write(line_x), Write(line_y))

        # 标出点[0.5, 0.5, 1.5]在xy轴平面上的投影点[0.5, 0.5, 0]
        dot_project = Dot(np.array([0.5, 0.5, 0]), color=YELLOW)
        self.play(Write(dot_project))

        # 连接过投影点平行于x、y轴的平行线和切面与x、y轴的交线的交点, 以及点和投影点连线
        line_project_x = DashedLine(dot_project.get_center(), [2, 0.5, 0], color=YELLOW)
        line_project_y = DashedLine(dot_project.get_center(), [0.5, 2, 0], color=YELLOW)
        dot_project_line = DashedLine(dot_project.get_center(), dot.get_center(), color=YELLOW)
        self.play(Write(line_project_x), Write(line_project_y), Write(dot_project_line))

        # 连接过点平行于x、y轴的平行线和切面与x、y轴的交线的交点
        line_x2 = Line(dot.get_center(), [2, 0.5, 0], color=YELLOW)
        line_y2 = Line(dot.get_center(), [0.5, 2, 0], color=YELLOW)
        self.play(Write(line_x2), Write(line_y2))
        self.play(FadeOut(tangent_plane))
        # x的偏导数
        self.play(WiggleOutThenIn(line_project_x), WiggleOutThenIn(line_x2))
        self.wait()
        # y的偏导数
        self.play(WiggleOutThenIn(line_project_y), WiggleOutThenIn(line_y2))

        # 梯度（那肯定是从投影点向切面和xy平面的交线做垂线，斜率最大）
        self.play(ApplyMethod(self.move_to, line_project_x.copy(), [0.5, 2, 0]))







class PerceptronWeightUpdate3(Scene):
    '''
    梯度下降和随机梯度下降 TODO：为什么使用梯度下降，而不是直接求最值
    '''
    captions = [
        '权重更新的依据我们知道了，就是让误分类点到直线的总距离最小'
        '那我们如何让距离函数最小，然后求得w和b呢？',
        '第一个方法：直接求最值，另其偏导等于0，然后求出w和b',
        '该方法较难，回想下以前我们求二元函数的极值时，极值判断和求解就非常困难，高维的就更困难了',
        '第二种方法：梯度下降，一般在机器学习里都会使用该方法',
        '梯度下降就是让自变量沿梯度的反方向搜索，从而寻找函数的最低点，也就是最小值。',
        '解释下"沿梯度的反方向搜索"，首先梯度就是导数，我们知道一个函数的导数大于0，在周围范围内函数值就是单调递增的',
        '所以我们想找到函数的最小值，那就让自变量往小了走，如果导数小于0，就往大了走',
    ]

    def construct(self):
        pass


class PerceptronWeightUpdate4(Scene):
    '''
    总距离表示和w、b每次更新的量
    '''


class PerceptronIris2(Scene):
    def construct(self):
        # 输入
        c1 = Circle(radius=0.4)
        c1.move_to(3 * LEFT + 2 * UP)
        c2 = Circle(radius=0.4)
        c2.next_to(c1, DOWN)
        c3 = Circle(radius=0.4)
        c3.next_to(c2, DOWN)
        c4 = Circle(radius=0.4)
        c4.next_to(c3, DOWN)
        x1 = TexMobject('x_{0}').scale(0.6)
        x1.move_to(c1.get_center())
        x2 = TexMobject('x_{1}').scale(0.6)
        x2.move_to(c2.get_center())
        x3 = TexMobject('x_{2}').scale(0.6)
        x3.move_to(c3.get_center())
        x4 = TexMobject('1').scale(0.6)
        x4.move_to(c4.get_center())

        self.play(Write(c1), Write(c2), Write(c3), Write(c4), Write(x1), Write(x2), Write(x3), Write(x4))
        # 累加
        layer1_gp = VGroup(c1, c2, c3, c4)
        add_c = Circle(radius=0.4)
        add_c.next_to(layer1_gp, 3 * RIGHT)
        sum = TexMobject('\\sum').scale(0.6)
        sum.move_to(add_c.get_center())
        # TODO：需要增加矩阵简写
        sum_detail = TexMobject('\\sum\\limits_{i=0}^{2}{w_{i}x_{i}}+b').scale(0.6)
        sum_detail.next_to(add_c, UP, aligned_edge=LEFT)
        self.play(Write(add_c), Write(sum))
        self.play(Write(sum_detail))
        # 权重连接
        l1 = Line(c1.get_center() + c1.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        w1 = TexMobject('w_{0}').scale(0.6)
        w1.move_to(l1.get_center())
        l2 = Line(c2.get_center() + c2.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        w2 = TexMobject('w_{1}').scale(0.6)
        w2.move_to(l2.get_center())
        l3 = Line(c3.get_center() + c3.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        w3 = TexMobject('w_{2}').scale(0.6)
        w3.move_to(l3.get_center())
        l4 = Line(c4.get_center() + c4.radius * RIGHT, add_c.get_center() - add_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        b = TexMobject('b').scale(0.6)
        b.move_to(l4.get_center())
        self.play(Write(l1), Write(l2), Write(l3), Write(l4), Write(w1), Write(w2), Write(w3), Write(b))
        # 激活函数
        fx_c = Circle(radius=0.4)
        fx_c.next_to(add_c, 3 * RIGHT)
        fx = TexMobject('f(x)').scale(0.6)
        fx.move_to(fx_c.get_center())
        self.play(Write(fx_c), Write(fx))
        # 解释激活函数形式
        fx_detail = TexMobject('f(x)=\\begin{cases}'
                               '1 & x>0\\\ '
                               '0 & x\\le0 \\\ '
                               '\\end{cases}').scale(0.6)
        fx_detail.next_to(fx_c, DOWN, aligned_edge=LEFT)
        self.play(Write(fx_detail))
        # 连接
        l5 = Line(add_c.get_center() + add_c.radius * RIGHT, fx_c.get_center() - fx_c.radius * RIGHT,
                  color=BLUE).set_opacity(0.8)
        self.play(Write(l5))
        # 输出
        c5 = Circle(radius=0.4)
        c5.next_to(fx_c, 3 * RIGHT)
        output = TextMobject('y').scale(0.6)
        output.move_to(c5.get_center())
        self.play(Write(c5), Write(output))
        # 连接
        l6 = Line(fx_c.get_center() + fx_c.radius * RIGHT, c5.get_center() - c5.radius * RIGHT, color=BLUE).set_opacity(
            0.8)
        self.play(Write(l6))

        # 创建感知器对象，动态模拟模型训练，同时也显示每次迭代错误率
        pn = Perceptron(eta=0.01, n_iter=10)
        # 读取数据
        df = pd.read_csv("assets/data/Iris.data", header=None)
        y = df.iloc[0:150, 4].values  # 类别
        X = df.iloc[0:150, [1,3]].values  # 花萼宽度、花瓣宽度
        pn.fit(X, y)
        # 权重和偏置的值
        weights = pn.weights_
        # 预测值
        predicts = pn.predicts_

        # 错误个数和迭代次数的关系
        errors = pn.errors_

        # 训练的这个直线的动态变化图
        axes = Axes(
            x_min=0,
            x_max=8,
            y_min=0,
            y_max=6,
            y_axis_config={"unit_size": 0.5}
        )
        count = 0
        x1_value_old = None
        x2_value_old = None
        w1_old = None
        w2_old = None
        b_old = None
        sum_old = None
        predict_old = None
        split_line_old = None
        for i in range(pn.n_iter):
            error = errors[i] # 错误个数【总共150条数据】
            for j in range(len(weights[i])):
                count +=1
                # 7个值
                x1_value = X[j][0]
                x2_value = X[j][1]
                weight = weights[i][j]
                w1 = weight[1]
                w2 = weight[2]
                b = weight[0]
                # 累加值
                sum = np.dot(X, [w1, w2]) + b
                # 预测值
                predict = predicts[i][j]

                x1_value_text = TextMobject(x1_value)
                x2_value_text = TextMobject(x2_value)
                w1_text = TextMobject(w1)
                w2_text = TextMobject(w2)
                b_text = TextMobject(b)
                sum_text = TextMobject(sum)
                predict_text = TextMobject(predict)

                # 分割线
                start = -weight[-1]/weight[1] * UP
                end = 8 * RIGHT - (weight[0]*8 + weight[-1])/weight[1] * UP
                split_line = Line(start, end)
                if count == 1:
                    self.play(x1.become(x1_value_text),
                              x2.become(x2_value_text),
                              w1.become(w1_text),
                              w2.become(w2_text),
                              b.become(b_text),
                              sum.become(sum_text),
                              output.become(predict_text))

                    self.play(ShowCreation(split_line))
                    self.wait(3)
                else:
                    self.play(split_line_old.become(split_line))

                x1_value_old = x1_value
                split_line_old = split_line


